package com.opensky.common.interfaces.frm;

import lombok.val;
import lombok.var;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.Assert;

/**
 * description 标准 Java Bean {@link Pageable} 实现，针对 offset 型分页
 *
 * @author ZY
 * @date 2021-05-05 17:49
 */
public final class PageRequestFrm implements Pageable {

    private final long offset;

    private final int limit;

    private final Sort sort;

    /**
     * 其值会在 getter 里被懒加载，不需要 volatile
     */
    private int page = -1;

    private PageRequestFrm(long offset, int limit, Sort sort) {
        this.offset = offset;
        this.limit = limit;
        this.sort = sort;
    }

    @Override
    public int getPageNumber() {
        // 无锁懒加载，不需要 volatile ，多线程下重复进入了 if 块也无所谓，反正只是个纯粹的简单数字计算，结果是唯一确定的
        var page = this.page;

        if (page < 0) {
            val pageNumber = offset / limit;

            // 参考 Math.toIntExact ，如果溢出则取 int 最大值
            this.page = page = (int) pageNumber == pageNumber ? (int) pageNumber : Integer.MAX_VALUE;
        }

        return page;
    }

    @Override
    public int getPageSize() {
        return limit;
    }

    @Override
    public long getOffset() {
        return offset;
    }

    @Override
    public Sort getSort() {
        return sort;
    }

    @Override
    public Pageable next() {
        return new PageRequestFrm(offset + limit, limit, sort);
    }

    @Override
    public Pageable previousOrFirst() {
        return hasPrevious() ? new PageRequestFrm(offset - limit, limit, sort) : first();
    }

    @Override
    public Pageable first() {
        return new PageRequestFrm(0, limit, sort);
    }

    @Override
    public boolean hasPrevious() {
        return offset >= limit;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof PageRequestFrm)) {
            return false;
        }

        val that = (PageRequestFrm) o;
        return offset == that.offset && limit == that.limit && sort.equals(that.sort);
    }

    @Override
    public int hashCode() {
        var result = (int) (offset ^ (offset >>> 32));
        result = 31 * result + limit;
        return 31 * result + sort.hashCode();
    }

    @Override
    public String toString() {
        return String.format("Offset request [offset: %d, size %d, sort: %s]", offset, limit, sort);
    }

    public static PageRequestFrm of(long offset, int limit) {
        return of(offset, limit, Sort.unsorted());
    }

    public static PageRequestFrm of(long offset, int limit, Sort sort) {
        Assert.isTrue(offset >= 0, "offset must be greater than or equal to 0");
        Assert.isTrue(limit > 0, "limit must be greater than 0");
        Assert.notNull(sort, "sort must not be null");

        return new PageRequestFrm(offset, limit, sort);
    }
}
